package com.clj.fastble.scan;

import com.clj.fastble.BleManager;
import com.clj.fastble.callback.BleScanAndConnectCallback;
import com.clj.fastble.callback.BleScanCallback;
import com.clj.fastble.callback.BleScanPresenterImp;
import com.clj.fastble.callback.BlueToothScanCallback;
import com.clj.fastble.data.BleDevice;
import com.clj.fastble.data.BleMsg;
import com.clj.fastble.data.BleScanState;
import com.clj.fastble.utils.BleLog;

import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.bluetooth.BluetoothHost;
import ohos.bluetooth.BluetoothRemoteDevice;
import ohos.bluetooth.ble.BleCentralManager;
import ohos.bluetooth.ble.BleCentralManagerCallback;
import ohos.bluetooth.ble.BleScanFilter;
import ohos.bluetooth.ble.BleScanResult;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.rpc.RemoteException;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;


/**
 * BleScanner
 */
public class BleScanner {
    private CommonEventSubscriber commonEventSubscriber = null;
    private BlueToothScanCallback blueToothScanCallback;
    private Set<BluetoothRemoteDevice> availableDevices = null;
    private BleScanCallback callback;
    /**
     * getInstance
     *
     * @return BleScanner
     */
    public static BleScanner getInstance() {
        return BleScannerHolder.S_BLESCANNER;
    }

    private static class BleScannerHolder {
        private static final BleScanner S_BLESCANNER = new BleScanner();
    }

    private BleScanState mBleScanState = BleScanState.STATE_IDLE;
    private BleCentralManager centralManager;

    private BleScanPresenter mBleScanPresenter =
            new BleScanPresenter() {
                @Override
                public void scanFailedEvent(int pos) {
                    mBleScanState = BleScanState.STATE_IDLE;
                }

                @Override
                public void groupScanResultsEvent(List<BleScanResult> list) {
                    mBleScanState = BleScanState.STATE_SCANNING;
                    mBleScanPresenter.notifyScanStarted(true);
                }

                @Override
                public void onScanStarted(boolean success) {
                    BleScanPresenterImp callback = mBleScanPresenter.getBleScanPresenterImp();
                    if (callback != null) {
                        callback.onScanStarted(success);
                    }
                }

                @Override
                public void onLeScan(BleDevice bleDevice) {
                    mBleScanState = BleScanState.STATE_SCANNING;
                    if (mBleScanPresenter.ismNeedConnect()) {
                        BleScanAndConnectCallback callback =
                                (BleScanAndConnectCallback) mBleScanPresenter.getBleScanPresenterImp();
                        if (callback != null) {
                            callback.onLeScan(bleDevice);
                        }
                    } else {
                        BleScanCallback callback = (BleScanCallback) mBleScanPresenter.getBleScanPresenterImp();
                        if (callback != null) {
                            callback.onLeScan(bleDevice);
                        }
                    }
                }

                @Override
                public void onScanning(BleDevice result) {
                    BleScanPresenterImp callback = mBleScanPresenter.getBleScanPresenterImp();
                    if (callback != null) {
                        callback.onScanning(result);
                    }
                }

                @Override
                public void onScanFinished(List<BleDevice> bleDeviceList) {
                    if (mBleScanPresenter.ismNeedConnect()) {
                        final BleScanAndConnectCallback callback =
                                (BleScanAndConnectCallback) mBleScanPresenter.getBleScanPresenterImp();
                        if (bleDeviceList == null || bleDeviceList.size() < 1) {
                            if (callback != null) {
                                callback.onScanFinished(null);
                            }
                        } else {
                            if (callback != null) {
                                callback.onScanFinished(bleDeviceList.get(0));
                            }
                            final List<BleDevice> list = bleDeviceList;
                            new EventHandler(EventRunner.getMainEventRunner())
                                    .postTask(
                                            new Runnable() {
                                                @Override
                                                public void run() {
                                                    BleManager.getInstance().connect(list.get(0), callback);
                                                }
                                            },
                                            100);
                        }
                    } else {
//                        BleScanCallback callback = (BleScanCallback) mBleScanPresenter.getBleScanPresenterImp();
//                        if (callback != null) {
//                            callback.onScanFinished(bleDeviceList);
//                        }
                    }
                }
            };

    /**
     * bleScan
     *
     * @param context  context
     * @param filters  filters
     * @param timeOut  timeOut
     * @param callback callback
     */
    public void scan(Context context, List<BleScanFilter> filters, long timeOut, final BleScanCallback callback) {
        startLeScan(context, filters, false, timeOut, callback);
    }

    /**
     * scanAndConnect
     *
     * @param context  context
     * @param filters  filters
     * @param timeOut  timeOut
     * @param callback callback
     */
    public void scanAndConnect(
            Context context, List<BleScanFilter> filters, long timeOut, BleScanCallback callback) {
        startLeScan(context, filters, true, timeOut, callback);
    }

    private synchronized void startLeScan(
            Context context, List<BleScanFilter> filters, boolean needConnect, long timeOut, BleScanCallback imp) {
        if (mBleScanState != BleScanState.STATE_IDLE) {
            BleLog.worn("scan action already exists, complete the previous scan action first");
            if (imp != null) {
                imp.onScanStarted(false);
            }
            return;
        }
        this.callback = imp;
//        mBleScanPresenter.prepare(context, needConnect, timeOut, imp);
        BleLog.info("BleScanResult", "ble1");
        centralManager = new BleCentralManager(context, new BleCentralManagerCallback() {
            @Override
            public void scanResultEvent(BleScanResult bleScanResult) {
                Map<Integer, byte[]> manu = bleScanResult.getManufacturerData();
                int key = 0;
                for (int keys : manu.keySet()) {
                    key = keys;
                }
                BleLog.info("BleScanResult", key + "---3");
                byte[] manuByte = manu.get(key);
                if (bleScanResult == null) {
                    return;
                }
                String manufacturerStr = bleScanResult.getPeripheralDevice().getDeviceAddr();

                BleDevice bleDevice =
                        new BleDevice(
                                bleScanResult.getPeripheralDevice(),
                                bleScanResult.getRssi(),
                                manuByte,
                                manufacturerStr,
                                bleScanResult.getTime(),
                                bleScanResult.isConnectable(),
                                bleScanResult.getAdvertiseFlag());
                callback.onScanning(bleDevice);
            }

            @Override
            public void scanFailedEvent(int i) {
                callback.onScanFinished();
            }

            @Override
            public void groupScanResultsEvent(List<BleScanResult> list) {

            }
        });
        // 创建扫描过滤器然后开始扫描
        // 过滤BLE扫描结果。
        // 可以设置过滤扫描结果的标准。

        // 构造过滤器需要地址，名称，服务数据，服务UUID和外围设备的制造商。
        if (filters == null) {
            filters = new ArrayList<BleScanFilter>();
        }
        centralManager.startScan(filters);
    }

    /**
     * stopLeScan
     */
    public synchronized void stopLeScan() {
        BleLog.info("bleScan", "stopLeScan");
        if (centralManager != null) {
            centralManager.stopScan();
            mBleScanState = BleScanState.STATE_IDLE;
            if (callback != null) {
                callback.onScanFinished();
            }
        }
    }

    /**
     * getScanState
     *
     * @return BleScanState
     */
    public BleScanState getScanState() {
        return mBleScanState;
    }

    /**
     * 普通蓝牙扫描
     *
     * @param context  context
     * @param callback callback
     */
    public void blueToothScan(Context context, BlueToothScanCallback callback) {
        this.blueToothScanCallback = callback;
        availableDevices = new LinkedHashSet<>();

        subscribeBluetoothEvents();
    }

    // 新增普通方式扫描

    /**
     * Subscribe for Events of Bluetooth using CommonEvents
     */
    public void subscribeBluetoothEvents() {
        MatchingSkills matchingSkills = new MatchingSkills();
        matchingSkills.addEvent(BluetoothHost.EVENT_HOST_STATE_UPDATE);
        matchingSkills.addEvent(BluetoothHost.EVENT_HOST_DISCOVERY_STARTED);
        matchingSkills.addEvent(BluetoothHost.EVENT_HOST_DISCOVERY_FINISHED);
        matchingSkills.addEvent(BluetoothRemoteDevice.EVENT_DEVICE_DISCOVERED);
        matchingSkills.addEvent(BluetoothRemoteDevice.EVENT_DEVICE_PAIR_STATE);
        CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
        commonEventSubscriber =
                new CommonEventSubscriber(subscribeInfo) {
                    @Override
                    public void onReceiveEvent(CommonEventData commonEventData) {
                        Intent intent = commonEventData.getIntent();
                        if (intent == null) {
                            return;
                        }
                        handleIntent(intent);
                    }
                };
        try {
            CommonEventManager.subscribeCommonEvent(commonEventSubscriber);
        } catch (RemoteException e) {
            BleLog.error("RemoteException in subscribeBluetoothEvents");
        }
    }

    /**
     * 扫描事件分类
     *
     * @param intent
     */
    private void handleIntent(Intent intent) {
        String action = intent.getAction();
        BleLog.info("scanEvent", "Action received : " + action);
        switch (action) {
            case BluetoothHost.EVENT_HOST_STATE_UPDATE: // 指示蓝牙状态更改时报告的事件
                handleHostStateUpdate();
                break;
            case BluetoothHost.EVENT_HOST_DISCOVERY_STARTED: // 表示蓝牙扫描开始时报告的事件
                handleDeviceDiscoveryState(true);
                break;
            case BluetoothRemoteDevice.EVENT_DEVICE_DISCOVERED: // 指示发现远程蓝牙设备时报告的事件
                handleBluetoothDeviceDiscovered(intent);
                break;
            case BluetoothHost.EVENT_HOST_DISCOVERY_FINISHED: // 表示蓝牙扫描完成时报告的事件
                BleLog.info("scanEvent", "EVENT_HOST_DISCOVERY_FINISHED: " + action);
                handleDeviceDiscoveryFinish();
                break;
            case BluetoothRemoteDevice.EVENT_DEVICE_PAIR_STATE: // 表示配对远程蓝牙设备时报告的事件
                handleDevicePairState(intent);
                break;
            default:
                BleLog.error("Action not handled : " + action);
        }
    }

    private void handleHostStateUpdate() {
        blueToothScanCallback.notifyBluetoothStatusChanged();
    }

    private void handleDeviceDiscoveryState(boolean isStarted) {
        blueToothScanCallback.notifyDiscoveryState(isStarted);
    }

    private void handleBluetoothDeviceDiscovered(Intent intent) {
        BluetoothRemoteDevice btRemoteDevice =
                intent.getParcelableParam(BluetoothRemoteDevice.REMOTE_DEVICE_PARAM_DEVICE);
        Optional<String> optName = btRemoteDevice.getDeviceName();
        BleLog.info("FastBle", "Name : " + optName.orElse(btRemoteDevice.getDeviceAddr()));
        BleLog.info("FastBle", "Address : " + btRemoteDevice.getDeviceAddr());
        availableDevices.add(btRemoteDevice);
        blueToothScanCallback.updateAvailableDevices(btRemoteDevice);
    }

    private void handleDeviceDiscoveryFinish() {
        blueToothScanCallback.updateFinishScanResult(getBlueToothDevices(availableDevices));
    }

    private void handleDevicePairState(Intent intent) {
        BluetoothRemoteDevice btRemoteDevice =
                intent.getParcelableParam(BluetoothRemoteDevice.REMOTE_DEVICE_PARAM_DEVICE);
        BleLog.info("Pair state : ", btRemoteDevice.getPairState() + "");
        if (btRemoteDevice.getPairState() == BluetoothRemoteDevice.PAIR_STATE_PAIRED) {
            blueToothScanCallback.updatePairedDevices(btRemoteDevice);
        } else if (btRemoteDevice.getPairState() == BluetoothRemoteDevice.PAIR_STATE_NONE) {
            blueToothScanCallback.updateFinishScanResult(getBlueToothDevices(availableDevices));
        }
    }

    /**
     * getBlueToothDevices
     *
     * @param remoteDeviceList remoteDeviceList
     * @return List
     */
    public List<BluetoothRemoteDevice> getBlueToothDevices(Set<BluetoothRemoteDevice> remoteDeviceList) {
        List<BluetoothRemoteDevice> btDevicesList = new ArrayList<>(remoteDeviceList);
        return btDevicesList;
    }

    /**
     * UnSubscribe for Bluetooth Events
     */
    public void unSubscribeBluetoothEvents() {
        if (commonEventSubscriber != null) {
            try {
                CommonEventManager.unsubscribeCommonEvent(commonEventSubscriber);
            } catch (RemoteException e) {
                BleLog.error("RemoteException in onUnsubscribe : ");
            }
            commonEventSubscriber = null;
        }
    }
}
